package jvs.vfs.opt.asset;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Hashtable;

import jvs.vfs.IFileBaseImpl;
import x.java.io.File;
import android.content.Context;
import android.content.res.AssetManager;

/**
 * @author qiangli
 * 
 */
public class AssetFileImpl extends IFileBaseImpl {
	
	private static class Entry {
		java.io.File file;
		String[] list;
		boolean isDirectory;
	}

	private static final String ASSET_CACHE_BASE = "asset.cache";

	private static Hashtable<String, Entry> cache = new Hashtable<String, Entry>();

	synchronized private static Entry load(String path) throws IOException {
		Entry val = cache.get(path);
		if (val != null) {
			return val;
		}
		Context ctx = (Context) fs.getContext();
		if (ctx == null) {
			throw new IOException("Context not set");
		}
		AssetManager am = ctx.getAssets();
		String[] list = am.list(path.substring(1)); //assets path is relative
		val = new Entry();
		val.list = list;
		val.isDirectory = (list.length > 0);
		cache.put(path, val);
		return val;
	}

	synchronized private static void ensureFile(String path, Entry entry) throws IOException {
		if (entry.file != null && entry.file.exists()) {
			return;
		}
		Context ctx = (Context) fs.getContext();
		if (ctx == null) {
			throw new IOException("Context not set");
		}
		AssetManager am = ctx.getAssets();
		try {
			String p = path.substring(1); //asset path is relative 
			java.io.File cacheDir = ctx.getDir(ASSET_CACHE_BASE, Context.MODE_PRIVATE);
			java.io.File file = new java.io.File(cacheDir, p);
			entry.file = file;
			if (entry.isDirectory) {
				file.mkdirs();
				return;
			}
			//file
			file.getParentFile().mkdirs();
			
			java.io.FileOutputStream os = new java.io.FileOutputStream(file);
			byte[] buf = new byte[File.BUFSIZE];
			InputStream is = am.open(p);
			while(true) {
				int read = is.read(buf);
				if (read == -1) {
					break;
				}
				if (read > 0) {
					os.write(buf, 0, read);
				}
			}
			is.close();
			os.close();
		} catch (FileNotFoundException e) {
			throw e;
		} catch (IOException e) {
			throw new FileNotFoundException(e.getMessage());
		}
	}

	private String path;

	private Entry entry;

	/**
	 * @param uri
	 */
	public AssetFileImpl(URI uri) {
		super(uri);
		//
		path = path(uri);
		
		try {
			entry = load(path);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public boolean create() {
		return false;
	}

	public boolean delete() {
		return false;
	}

	@Override
	public boolean canRead() {
		return entry != null;
	}

	@Override
	public boolean canWrite() {
		return false;
	}

	public boolean exists() {
		return entry != null;
	}

	public InputStream getInputStream() {
		try {
			ensureFile(path, entry);
			return new java.io.FileInputStream(entry.file);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public OutputStream getOutputStream() {
		return null;
	}

	public boolean isDirectory() {
		return entry.isDirectory;
	}

	@Override
	public boolean isEmpty() {
		return entry.list.length == 0;
	}

	@Override
	public boolean isFile() {
		return entry.isDirectory == false;
	}

	public String[] list() {
		return entry.list;
	}

	public boolean mkdir() {
		return false;
	}

	public boolean move(URI uri) {
		return false;
	}

	public long getLength() {
		try {
			ensureFile(path, entry);
			return entry.file.length();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return -1;
	}

	public String getContent() {
		try {
			ensureFile(path, entry);
			InputStream is = new java.io.FileInputStream(entry.file);
			int size = (int)entry.file.length();
			byte[] buf = new byte[size];
			is.read(buf);
			is.close();
			String s = new String(buf);
			return s;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public boolean isLocal() {
		return true;
	}

	@Override
	public boolean isNative() {
		//backed by local file
		return true;
	}

	@Override
	public URI toRealURI() {
		try {
			ensureFile(path, entry);
			return entry.file.toURI();
		} catch (Exception e) {
			e.printStackTrace();
		}
		//?
		return super.toRealURI();
	}
}
